/*
 * (c) Thomas Pasch, 2003. All Rights Reserved.
 *
 * --LICENSE NOTICE--
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of license of JSch
 * (http://www.jcraft.com/jsch/).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * --LICENSE NOTICE--
 */

package de.mud.jta.plugin;

import de.mud.jta.JSchResourceManager;
import de.mud.jta.Plugin;
import de.mud.jta.FilterPlugin;
import de.mud.jta.PluginBus;
import de.mud.jta.PluginConfig;
import de.mud.jta.ResourceManager;

import de.mud.jta.VisualPlugin;

import de.mud.jta.event.ConfigurationListener;
import de.mud.jta.event.OnlineStatusListener;
import de.mud.jta.event.TerminalTypeRequest;
import de.mud.jta.event.WindowSizeRequest;
import de.mud.jta.event.LocalEchoRequest;

import de.mud.ssh.SshIO;

import javax.swing.*;

import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.util.Locale;

import de.mud.jta.event.SocketListener;
import de.mud.jta.event.OnlineStatus;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.ProxyHTTP;
import com.jcraft.jsch.ProxySOCKS5;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;

/**
 * Secure Shell plugin for the Java Telnet Application (JTA). This is a plugin
 * to be used instead of Telnet for secure remote terminal sessions over
 * insecure networks.
 * <p>
 * This plugin works in conjuction with JSch, a open source client site pure
 * java implementation of the SSH protocol version 2 with a BSD styled license.
 * </p>
 * <p>
 * This plugin was written mainly because JSch is a more complete implementation
 * of SSH2 than the SSH plugin that comes with JTA. JSch comes with proxy and
 * socks5 support, zlib compression and X11 forwarding. After copying the
 * compiled class file of this source to de.mud.jta.plugin, it is invoked like
 * this:
 * </p>
 * <p>
 * <code>
 * java -cp <include jta and jsch here> de.mud.jta.Main -plugins Status,JTAJSch,Terminal [hostname]
 * </code>
 * </p>
 * <p>
 * Known Bugs:<br/>
 * <ul>
 * <li></li>
 * <li>No SFTP support.</li>
 * </ul>
 * </p>
 * <p>
 * <b>Maintainer:</b> G S Hiremath <gshiremath@gmail.com>
 * </p>
 * 
 * @see <a href="https://code.google.com/p/javassh-plugin/">Java SSH
 *      Plugin</a>
 * @see <a href="http://www.jcraft.com/jsch/">JSch homepage</a>
 * @see <a href="http://www.openssh.org/">free Open Source version of SSH</a>
 * @see <a href="http://www.ssh.com/">Official SSH homepage</a>
 * @version $Id:$
 * @author G S Hiremath <gshiremath@mail.com>
 */
public class JTAJSch extends Plugin implements FilterPlugin, VisualPlugin {

	private final JSchResourceManager rm = JSchResourceManager.getInstance();
	//private final ResourceManager rm = ResourceManager.getInstance();
	
	private final String MENU = rm.getString("jta.jsch.menu.options");
	private final String MENU_HTTP_PROXY = rm.getString("jta.jsch.menu.httpproxy");
	private final String MENU_SOCKS_PROXY = rm.getString("jta.jsch.menu.socksproxy");
	private final String MENU_X11 = rm.getString("jta.jsch.menu.x11");
	private final String MENU_LOCAL_PORT = rm.getString("jta.jsch.menu.localport");
	private final String MENU_REMOTE_PORT = rm.getString("jta.jsch.menu.remoteport");

	private static final JSch jSch_ = new JSch();

	private String proxyHttpHost_ = null;
	private int proxyHttpPort_;
	private String proxySOCKS5Host_ = null;
	private int proxySOCKS5Port_;
	private String xHost_ = null;
	private int xPort_;

	protected FilterPlugin source;
	// protected SshIO handler;
	protected Session session_;
	private Channel channel_;
	private InputStream in_;
	private OutputStream out_;
	private String host_;
	private int port_;

	// protected String user, pass;

	private final static int debug = 0;

	private volatile boolean auth = false;

	protected MyUserInfo userInfo_;

	/**
	 * Create a new ssh plugin.
	 */
	public JTAJSch(final PluginBus bus, final String id) {
		super(bus, id);

		// create a new telnet protocol handler
		// handler = new SshIO() {
		// /** get the current terminal type */
		// public String getTerminalType() {
		// return (String)bus.broadcast(new TerminalTypeRequest());
		// }
		// /** get the current window size */
		// public Dimension getWindowSize() {
		// return (Dimension)bus.broadcast(new WindowSizeRequest());
		// }
		// /** notify about local echo */
		// public void setLocalEcho(boolean echo) {
		// bus.broadcast(new LocalEchoRequest(echo));
		// }
		// /** write data to our back end */
		// public void write(byte[] b) throws IOException {
		// source.write(b);
		// }
		// };

		bus.registerPluginListener(new ConfigurationListener() {
			public void setConfiguration(PluginConfig config) {
				 String user = System.getProperty("SSH.user");
				//String user = config.getProperty("SSH", id, "user");
				String pass = config.getProperty("SSH", id, "password");
				userInfo_ = new MyUserInfo(user, pass, null);
			}
		});
		
		// reset the protocol handler just in case :-)
		bus.registerPluginListener(new OnlineStatusListener() {

			public void online() {
				String user = null, pass = null;
				if (userInfo_ != null) {
					user = userInfo_.getUser();
					pass = userInfo_.getPassword();
				}
				if ((user == null) || (user.trim().length() == 0)) 
				{
					boolean firstEntry=true;
					while(true)
					{
						String warnMessage="";
						if(!firstEntry)
						{
							warnMessage = rm.getString("jta.jsch.enter.username");	
						}
						
						String userName = promptForUserName(rm.getString("jta.jsch.sshuser"),"",warnMessage,rm.getString("jta.jsch.username"));
						if(userName !=null && userName.length() > 0)
						{
							userInfo_ = new MyUserInfo(userName, null,null);
							connect();
							break;
						}
						else if(userName == null)
						{
							break;
						}
						firstEntry = false;
					}
/*					
					Panel panel = new Panel(new GridLayout(1, 1));
					final TextField login = new TextField(user, 10);
					panel.add(new Label(rm.getString("jta.jsch.username")));
					panel.add(login);
					frame.add("Center", panel);
					panel = new Panel();
					Button cancel = new Button(rm.getString("jta.button.cancel"));
					Button ok = new Button(rm.getString("jta.button.login"));
					ActionListener enter = new ActionListener() {
						public void actionPerformed(ActionEvent evt) {
							// handler.setLogin(login.getText());
							// handler.setPassword(passw.getText());
							
									null);
							frame.dispose();
							connect();
						}
					};
					ok.addActionListener(enter);
					cancel.addActionListener(new ActionListener() {
						public void actionPerformed(ActionEvent evt) {
							frame.dispose();
						}
					});
					panel.add(cancel);
					panel.add(ok);
					frame.add("South", panel);

					frame.pack();
					frame.show();
					frame.setLocation(frame.getToolkit().getScreenSize().width
							/ 2 - frame.getSize().width / 2, frame.getToolkit()
							.getScreenSize().height
							/ 2 - frame.getSize().height / 2);
*/				} else {
					error(user + ":" + pass);
					// handler.setLogin(user);
					// handler.setPassword(pass);
					userInfo_ = new MyUserInfo(user, pass, null);
					connect();
				}
			}

			private void disconnect() {
				channel_.disconnect();
			}

			private void connect() 
			{
				if (auth) 
				{
					throw new IllegalStateException();
				}
				try 
				{
					session_ = jSch_.getSession(userInfo_.getUser(), host_,
							port_);
					session_.setUserInfo(userInfo_);
					if (isProxyHttp()) 
					{
						session_.setProxy(new ProxyHTTP(proxyHttpHost_,
								proxyHttpPort_));
					}
					else if (isProxySOCKS5()) 
					{
						session_.setProxy(new ProxySOCKS5(proxySOCKS5Host_,
								proxySOCKS5Port_));
					}
					if (isXForwarding()) 
					{
						session_.setX11Host(xHost_);
						session_.setX11Port(xPort_);
					}
					session_.connect();
					channel_ = session_.openChannel("shell");
					try 
					{
						in_ = channel_.getInputStream();
						out_ = channel_.getOutputStream();
						out_.flush();
					}
					catch (IOException ee) 
					{
						ee.printStackTrace();
						throw new RuntimeException(ee);
					}
					channel_.connect();
				}
				catch (JSchException e) 
				{
					createErrorFrame(e,host_,port_);
					e.printStackTrace();
					throw new RuntimeException(e);
				}
				auth = true;
			}

			public void offline() 
			{
				// handler.disconnect();
				try 
				{
					if (in_ != null)
						in_.close();
					if (out_ != null)
						out_.close();
				}
				catch (IOException e) 
				{
					e.printStackTrace();
					throw new RuntimeException(e);
				}
				finally 
				{
					auth = false;
					userInfo_ = null;
					if (channel_ != null)
						channel_.disconnect();
					if (session_ != null)
						session_.disconnect();
				}
			}
		});

		bus.registerPluginListener(new SocketListener() {
			public void connect(String host, int port) {
				host_ = host;
				port_ = port;
				bus.broadcast(new OnlineStatus(true));
			}

			public void disconnect() {
				bus.broadcast(new OnlineStatus(false));
			}
		});
	}

	private String promptForUserName(String destination, String name,
			String instruction,String prompt)
	{
		Container panel = new JPanel();
		panel.setLocale(rm.getLocale());
		panel.setLayout(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1,
				GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(
						0, 0, 0, 0), 0, 0);
		gbc.weightx = 1.0;
		gbc.gridwidth = GridBagConstraints.REMAINDER;
		gbc.gridx = 0;
		panel.add(new JLabel(instruction), gbc);
		gbc.gridy++;

		gbc.gridwidth = GridBagConstraints.RELATIVE;

		JTextField text = new JTextField();
		gbc.fill = GridBagConstraints.NONE;
		gbc.gridx = 0;
		gbc.weightx = 1;
		panel.add(new JLabel(prompt),gbc);

		gbc.gridx = 1;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		gbc.weighty = 1;
			text = new JTextField(20);
		panel.add(text, gbc);
		gbc.gridy++;
		JOptionPane.setDefaultLocale(rm.getLocale());
		if (JOptionPane.showConfirmDialog(null, panel, destination + ": "
				+ name, JOptionPane.OK_CANCEL_OPTION,
				JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) 
		{
			String response = "";
			response = text.getText();
			return response;
		}
		else 
		{
			return null; // cancel
		}
	
	}
	private void createErrorFrame(Exception ex,String hostName,int port)
	{
		String errMsg="";
		String [] params = new String[] {hostName,Integer.toString(port)};
		if(ex.getMessage().contains("UnknownHostException"))
		{
			errMsg=rm.getString("jta.error.unknownhost")+hostName;
		}
		else if(ex.getMessage().contains("ConnectException"))
		{
			
			errMsg=rm.getString("jta.error.connection.refused",params);
		}
		else if(ex.getMessage().contains("NoRouteToHostException"))
		{
			errMsg=rm.getString("jta.error.connection.noroute",params);
		}
		else
		{
			errMsg=ex.getLocalizedMessage();
		}
		JOptionPane.setDefaultLocale(rm.getLocale());
		JOptionPane.showMessageDialog(null,errMsg);

/*		final Frame frame = new Frame(rm.getString("jta.label.error"));
		Panel panel = new Panel(new GridLayout(2, 2));
		panel.add(new Label(rm.getString("jta.label.connectionException")));
		frame.add("North", panel);
		panel = new Panel();
		panel.add(new Label(errMsg));
		frame.add("Center", panel);
		panel = new Panel();
		Button ok = new Button(rm.getString("jta.button.ok"));
		ok.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				frame.dispose();
			}
		});
		panel.add(ok);
		frame.add("South", panel);

		frame.pack();
		frame.show();
		frame.setLocation(frame.getToolkit().getScreenSize().width
				/ 2 - frame.getSize().width / 2, frame.getToolkit()
				.getScreenSize().height
				/ 2 - frame.getSize().height / 2);
*/		
				bus.broadcast(new OnlineStatus(false));
	
	}
	public void setFilterSource(FilterPlugin source) {
		if (debug > 0)
			System.err.println("ssh: connected to: " + source);
		this.source = source;
	}

	public FilterPlugin getFilterSource() {
		return source;
	}

	private byte buffer[];
	private int pos;

	/**
	 * Read data from the backend and decrypt it. This is a buffering read as
	 * the encrypted information is usually smaller than its decrypted pendant.
	 * So it will not read from the backend as long as there is data in the
	 * buffer.
	 * 
	 * @param b
	 *            the buffer where to read the decrypted data in
	 * @return the amount of bytes actually read.
	 */
	public int read(byte[] b) throws IOException {
		// we don't want to read from the pipeline without authorization
		while (!auth)
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		// Empty the buffer before we do anything else
		if (buffer != null) {
			int amount = ((buffer.length - pos) <= b.length) ? buffer.length
					- pos : b.length;
			System.arraycopy(buffer, pos, b, 0, amount);
			if (pos + amount < buffer.length) {
				pos += amount;
			} else
				buffer = null;
			return amount;
		}

		// now that the buffer is empty let's read more data and decrypt it
		// int n = source.read(b);
		// if(n > 0) {
		int n;
		final int length = b.length;
		if ((n = in_.available()) > 0) {
			if (n > length)
				n = length;
			n = in_.read(b, 0, n);
			byte[] tmp = new byte[n];
			System.arraycopy(b, 0, tmp, 0, n);
			pos = 0;
			// buffer = handler.handleSSH(tmp);
			buffer = tmp;

			if (debug > 0 && buffer != null && buffer.length > 0)
				System.err.println("ssh: " + buffer);

			if (buffer != null && buffer.length > 0) {
				if (debug > 0)
					System.err.println("ssh: incoming=" + n + " now="
							+ buffer.length);
				int amount = buffer.length <= b.length ? buffer.length
						: b.length;
				System.arraycopy(buffer, 0, b, 0, amount);
				pos = n = amount;
				if (amount == buffer.length) {
					buffer = null;
					pos = 0;
				}
			} else
				return 0;
		}
		return n;
	}

	/**
	 * Write data to the back end. This hands the data over to the ssh protocol
	 * handler who encrypts the information and writes it to the actual back end
	 * pipe.
	 * 
	 * @param b
	 *            the unencrypted data to be encrypted and sent
	 */
	public void write(byte[] b) throws IOException {
		// no write until authorization is done
		if (!auth)
			return;
		for (int i = 0; i < b.length; i++) {
			switch (b[i]) {
			case 10: /* \n -> \r */
				b[i] = 13;
				break;
			}
		}
		// handler.sendData(new String(b));
		out_.write(b);
		out_.flush();
	}

	public JComponent getPluginVisual() {
		// JTextArea comp = new JTextArea("getPluginVisual");
		return null;
	}

	public JMenu getPluginMenu() {
		final JMenu menu = new JMenu(MENU);
		JMenuItem item = null;
/*		JMenuItem item = new JMenuItem(MENU_HTTP_PROXY);
		item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String foo = getProxyHttpHost();
				int bar = getProxyHttpPort();
				String proxy = JOptionPane.showInputDialog(
						"HTTP proxy server (hostname:port)",
						((foo != null && bar != 0) ? foo + ":" + bar : ""));
				if (proxy == null)
					return;
				if (proxy.length() == 0) {
					setProxyHttp(null, 0);
					return;
				}
				try {
					foo = proxy.substring(0, proxy.indexOf(':'));
					bar = Integer.parseInt(proxy
							.substring(proxy.indexOf(':') + 1));
					if (foo != null) {
						setProxyHttp(foo, bar);
					}
				} catch (Exception ee) {
				}
			}
		});
		menu.add(item);

		item = new JMenuItem(MENU_SOCKS_PROXY);
		item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String foo = getProxySOCKS5Host();
				int bar = getProxySOCKS5Port();
				String proxy = JOptionPane.showInputDialog(
						"SOCKS5 server (hostname:1080)",
						((foo != null && bar != 0) ? foo + ":" + bar : ""));
				if (proxy == null)
					return;
				if (proxy.length() == 0) {
					setProxySOCKS5(null, 0);
					return;
				}
				try {
					foo = proxy.substring(0, proxy.indexOf(':'));
					bar = Integer.parseInt(proxy
							.substring(proxy.indexOf(':') + 1));
					if (foo != null) {
						setProxySOCKS5(foo, bar);
					}
				} catch (Exception ee) {
				}
			}
		});
		menu.add(item);
*/
		item = new JMenuItem(MENU_X11);
		item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JOptionPane.setDefaultLocale(rm.getLocale());
				String display = JOptionPane.showInputDialog(rm.getString("jta.message.x11.message"),
						(getXHost() == null) ? "": (getXHost() + ":" + getXPort()));
				try {
					if (display != null) {
						String host = display
								.substring(0, display.indexOf(':'));
						int port = Integer.parseInt(display.substring(display
								.indexOf(':') + 1));
						setXForwarding(host, port);
					}
				} catch (Exception ee) {
					setXForwarding(null, -1);
				}
			}
		});
		menu.add(item);

		item = new JMenuItem(MENU_LOCAL_PORT);
		item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JOptionPane.setDefaultLocale(rm.getLocale());
				if (session_ == null) {
					JOptionPane.showMessageDialog(menu.getParent(),rm.getString("jta.message.connection.required"));
					return;
				}
				try {
					String title = rm.getString("jta.message.localport.title");
					String foo = JOptionPane.showInputDialog(title, "");
					if (foo == null)
						return;
					int port1 = Integer.parseInt(foo.substring(0, foo
							.indexOf(':')));
					foo = foo.substring(foo.indexOf(':') + 1);
					String host = foo.substring(0, foo.indexOf(':'));
					int port2 = Integer.parseInt(foo
							.substring(foo.indexOf(':') + 1));
					setPortForwardingL(port1, host, port2);
				} catch (Exception ee) {
				}
			}
		});
		menu.add(item);

		item = new JMenuItem(MENU_REMOTE_PORT);
		item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JOptionPane.setDefaultLocale(rm.getLocale());
				if (session_ == null) {
					JOptionPane.showMessageDialog(menu.getParent(),rm.getString("jta.message.connection.required"));
					return;
				}
				try {
					String title = rm.getString("jta.message.remoteport.title");
					String foo = JOptionPane.showInputDialog(title, "");
					if (foo == null)
						return;
					int port1 = Integer.parseInt(foo.substring(0, foo
							.indexOf(':')));
					foo = foo.substring(foo.indexOf(':') + 1);
					String host = foo.substring(0, foo.indexOf(':'));
					int port2 = Integer.parseInt(foo
							.substring(foo.indexOf(':') + 1));
					setPortForwardingR(port1, host, port2);
				} catch (Exception ee) {
				}
			}
		});
		menu.add(item);

		return menu;
	}

	private boolean isProxyHttp() {
		return proxyHttpHost_ != null;
	}

	private String getProxyHttpHost() {
		return proxyHttpHost_;
	}

	private int getProxyHttpPort() {
		return proxyHttpPort_;
	}

	private void setProxyHttp(String host, int port) {
		proxyHttpHost_ = host;
		switch (port) {
		case -1:
			proxyHttpHost_ = null;
			proxyHttpPort_ = port;
			break;
		case 0:
			proxyHttpPort_ = 3128;
			break;
		default:
			proxyHttpPort_ = port;
		}
	}

	private boolean isProxySOCKS5() {
		return proxySOCKS5Host_ != null;
	}

	private String getProxySOCKS5Host() {
		return proxySOCKS5Host_;
	}

	private int getProxySOCKS5Port() {
		return proxySOCKS5Port_;
	}

	private void setProxySOCKS5(String host, int port) {
		proxySOCKS5Host_ = host;
		switch (port) {
		case -1:
			proxySOCKS5Host_ = null;
			proxySOCKS5Port_ = port;
			break;
		case 0:
			proxySOCKS5Port_ = 3128;
			break;
		default:
			proxySOCKS5Port_ = port;
		}
	}

	private boolean isXForwarding() {
		return xHost_ != null;
	}

	private String getXHost() {
		return xHost_;
	}

	private int getXPort() {
		return xPort_;
	}

	private void setXForwarding(String host, int port) {
		xHost_ = host;
		switch (port) {
		case -1:
			xHost_ = null;
			xPort_ = port;
			break;
		default:
			xPort_ = port;
		}
	}

	private void setPortForwardingR(int port1, String host, int port2) {
		try {
			session_.setPortForwardingR(port1, host, port2);
		} catch (JSchException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	private void setPortForwardingL(int port1, String host, int port2) {
		try {
			session_.setPortForwardingL(port1, host, port2);
		} catch (JSchException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

}

final class MyUserInfo implements UserInfo, UIKeyboardInteractive {

	private static final JSchResourceManager rm = JSchResourceManager.getInstance();
	private static final Locale loc = rm.getLocale();

	public MyUserInfo(String user, String password, String passphrase) {
		user_ = user;
		password_ = password;
		passphrase_ = passphrase;
	}

	public String getUser() {
		return user_;
	}

	private final String user_;

	public String getPassword() {
		return password_;
	}

	private String password_;

	public boolean promptYesNo(String message) {
		JOptionPane.setDefaultLocale(loc);
		Object[] options = { rm.getString("jta.button.yes"), rm.getString("jta.button.no") };
		
		int foo = JOptionPane.showOptionDialog(null, message, 
				rm.getString("jta.message.warning"),JOptionPane.DEFAULT_OPTION, 
					JOptionPane.WARNING_MESSAGE, null,options, options[0]);
		return foo == 0;
	}

	JTextField passwordField = (JTextField) new JPasswordField(20);

	public boolean promptPassword(String message) {
		passwordField.setDefaultLocale(loc);
		Object[] ob = { passwordField };
		int result = JOptionPane.showConfirmDialog(null, ob, message,
				JOptionPane.OK_CANCEL_OPTION);
		if (result == JOptionPane.OK_OPTION) {
			password_ = passwordField.getText();
			return true;
		} else {
			return false;
		}
	}

	public void showMessage(String message) {
		JOptionPane.setDefaultLocale(loc);
		JOptionPane.showMessageDialog(null, message);
	}

	private final String passphrase_;

	public String getPassphrase() {
		return passphrase_;
	}

	public boolean promptPassphrase(String message) {
		return false;
	}

	final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1,
			GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(
					0, 0, 0, 0), 0, 0);
	private Container panel;

	public String[] promptKeyboardInteractive(String destination, String name,
			String instruction, String[] prompt, boolean[] echo) {
		/*
		 * //System.out.println("promptKeyboardInteractive");
		 * System.out.println("destination: "+destination);
		 * System.out.println("name: "+name);
		 * System.out.println("instruction: "+instruction);
		 * System.out.println("prompt.length: "+prompt.length);
		 * System.out.println("prompt: "+prompt[0]);
		 */
		panel = new JPanel();
		panel.setLocale(loc);
		panel.setLayout(new GridBagLayout());

		gbc.weightx = 1.0;
		gbc.gridwidth = GridBagConstraints.REMAINDER;
		gbc.gridx = 0;
		panel.add(new JLabel(instruction), gbc);
		gbc.gridy++;

		gbc.gridwidth = GridBagConstraints.RELATIVE;

		JTextField[] texts = new JTextField[prompt.length];
		for (int i = 0; i < prompt.length; i++) {
			gbc.fill = GridBagConstraints.NONE;
			gbc.gridx = 0;
			gbc.weightx = 1;
			panel.add(new JLabel(prompt[i]), gbc);

			gbc.gridx = 1;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.weighty = 1;
			if (echo[i]) {
				texts[i] = new JTextField(20);
			} else {
				texts[i] = new JPasswordField(20);
			}
			panel.add(texts[i], gbc);
			gbc.gridy++;
		}
		JOptionPane.setDefaultLocale(loc);
		if (JOptionPane.showConfirmDialog(null, panel, destination + ": "
				+ name, JOptionPane.OK_CANCEL_OPTION,
				JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {
			String[] response = new String[prompt.length];
			for (int i = 0; i < prompt.length; i++) {
				response[i] = texts[i].getText();
			}
			return response;
		} else {
			return null; // cancel
		}
	}
}
